﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

using Sherwood.SignOn.Server.Models;
using Sherwood.Security;
using Sherwood.SignOn.Server.Models.Repositories;
using Sherwood.Mvc;
using Sherwood.SignOn.Server.Models.Services;
using Sherwood.SignOn.Server.Content;

namespace Sherwood.SignOn.Server.Models.ViewModels
{
    public class SignInViewModel : ViewModelBase
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public string ClientSessionId { get; set; }
        public string ReturnUrl { get; set; }
        public string SignOutUrl { get; set; }
        public string RequireLogin { get; set; }
        public bool RequireNonPersistentSession { get; set; }
        public bool TestCookieWritten { get; set; }
        public bool RememberMe { get; set; }


        /// <summary>
        /// Returns the ReturnUrl but without macro parameters such as {signinticket}, {signinsignature}.
        /// This can typically be used to maintain the return URL accross password renewal tests (where the client session is likely
        /// to have expired by the time the link is called.
        /// In practice, the client would probably redirect to login again since no ticket is provided.
        /// </summary>
        /// <returns></returns>
        public string GetReturnUrlWithoutTicket()
        {
            Regex regexParameter = new Regex("[^\\?&=]+={(signin|signout)(signature|ticket)}&?");
            string returnUrl = ReturnUrl != null ? ReturnUrl : "";
            MatchCollection matches = regexParameter.Matches(returnUrl);
            foreach (Match match in matches)
            {
                if (match.Value != null)
                {
                    returnUrl = returnUrl.Replace(match.Value, "");
                }
            }
            return returnUrl;
        }

        /// <summary>
        /// Returns the ReturnUrl with a valid ticket and signature.
        /// The following macros are replaced:
        /// {signinticket}
        /// {signinsignature}
        /// </summary>
        /// <returns>ReturnUrl with macro replacements</returns>
        public string GetParameterizedReturnUrl(IUserSession session)
        {
            if (string.IsNullOrEmpty(ReturnUrl) || session == null)
            {
                return Config.Settings.DefaultRedirectUrl;
            }
            else
            {
                string returnUrl = ReturnUrl;
                string signature = null;
                string ticket = UserSessionHelper.GetSignInTicket(session, ClientSessionId, out signature);
                string urlTicket = HttpUtility.UrlEncode(ticket);
                string urlSignature = HttpUtility.UrlEncode(signature);
                returnUrl = returnUrl.Replace("{signinticket}", urlTicket).Replace("{signinsignature}", urlSignature);
                return returnUrl;
            }

        }



        /// <summary>
        /// Attempts to authenticate user either using username/password or automatically by checking for existing user session.
        /// </summary>
        /// <param name="state"></param>
        /// <returns>True if user has been authenticated. False if user could not be authenticated.</returns>
        public bool AuthenticateUser(ModelStateDictionary state)
        {
            bool authenticated = false;
            bool requireLogin = true;
            if (!string.IsNullOrEmpty(RequireLogin))
                bool.TryParse(RequireLogin, out requireLogin);
            string ipAddress = HttpContext.Current != null && HttpContext.Current.Request != null && HttpContext.Current.Request.UserHostAddress != null ? HttpContext.Current.Request.UserHostAddress : "";
            string signOutUrl = SignOutUrl ?? "";
            string clientSessionId = ClientSessionId ?? "";

            //If username or password have been provided, attempt to authenticate (regardless of whether an existing session exists)
            if (!string.IsNullOrEmpty(UserName) || !string.IsNullOrEmpty(Password))
            {
                //Since the user is providing a new username/password, we want to end any existing sessions.
                UserSessionHelper.EndCurrentUserSession();

                if (string.IsNullOrEmpty(UserName))
                {
                    state.AddModelError("UserName", Translations.SignIn_Enter_Username);
                }
                else
                {
                    if (string.IsNullOrEmpty(Password))
                    {
                        state.AddModelError("UserName", Translations.SignIn_Enter_Password);
                    }
                }
                if (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password))
                {
                    IUserAccount user = Data.UserAccounts.AuthenticateUser(UserName, Password);
                    if (user == null)
                    {
                        state.AddModelError("", Translations.SignIn_Invalid_Username_Or_Password);
                    }
                    else
                    {
                        if (user.VerificationStatus == VerificationStatus.Unverified)
                        {
                            state.AddModelError("", Translations.SignIn_Email_Not_Verified.Parameterize(new { SendVerificationUrl = "/UserAccount/SendVerification/?UserAccountId=" + user.Id + "&RequestedUrl=" + HttpUtility.UrlEncode(GetReturnUrlWithoutTicket()) }, Config.Settings));
                            authenticated = false;
                        }
                        else
                        {

                            //Register session
                            UserSessionHelper.CurrentUserSessionId = UserSessionHelper.StartUserSession(user.Id, ipAddress, RememberMe);
                            Guid userSessionId = UserSessionHelper.CurrentUserSessionId;
                            if (clientSessionId != "" || signOutUrl != "")
                            {
                                //Register client session
                                Data.UserSessions.AddUserSessionClientRecord(userSessionId, clientSessionId, signOutUrl);
                            }
                            authenticated = true;

                        }
                    }
                }
            }
            else
            {
                IUserSession session = UserSessionHelper.GetCurrentUserSession();
                if (session == null)
                {
                    if (!requireLogin)
                    {
                        //User is not logged on, but the client application doesn't require login
                        //So we want to return a session with no user... (i.e. create an annonymous session without login)

                        Guid userAccountId = Guid.Empty;

                        //Register session
                        UserSessionHelper.CurrentUserSessionId = UserSessionHelper.StartUserSession(userAccountId, ipAddress, RememberMe);
                        Guid userSessionId = UserSessionHelper.CurrentUserSessionId;
                        if (clientSessionId != "" || signOutUrl != "")
                        {
                            //Register client session
                            Data.UserSessions.AddUserSessionClientRecord(userSessionId, clientSessionId, signOutUrl);
                        }

                        authenticated = true;

                    }
                    else
                    {
                        //AppSession.Clear(); //end session since usersession doesn't exist anymore.
                    }
                }
                else
                {
                    //If the current session is persistent and a non-persistent session is required, the user should be signed out and the login page displayed.
                    if (RequireNonPersistentSession && UserSessionHelper.IsPersistentSession)
                    {
                        UserSessionHelper.EndCurrentUserSession();
                        return false;
                    }

                    //Check that session is not annonymous, or that we don't require a login
                    if (session.UserAccount != null || !requireLogin) 
                    {
                        //User is already authenticated
                        if (clientSessionId != "" || signOutUrl != "")
                        {
                            //Register client session
                            Data.UserSessions.AddUserSessionClientRecord(session.Id, clientSessionId, signOutUrl);
                        }
                        authenticated = true;
                    }
                    else
                    {
                        //AppSession.Clear(); //we require a login, but the current session was an annonymous session (no user id)
                    }
                }
            }
            return authenticated;
        }

        
    }
}
